/**************************************************************************************************
 * red library                                                                                    *
 * Copyright © 2012 David Kretzmer                                                                *
 *                                                                                                *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software  *
 * and associated documentation files (the "Software"), to deal in the Software without           *
 * restriction,including without limitation the rights to use, copy, modify, merge, publish,      *
 * distribute,sublicense, and/or sell copies of the Software, and to permit persons to whom the   *
 * Software is furnished to do so, subject to the following conditions:                           *
 *                                                                                                *
 * The above copyright notice and this permission notice shall be included in all copies or       *
 * substantial portions of the Software.                                                          *
 *                                                                                                *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING  *
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND     *
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.        *
 *                                                                                                *
 *************************************************************************************************/

#pragma once

#include "red/static_vector.hpp"
#include "light/utility/type_traits.hpp"


namespace red
{
	//=============================================================================================
	// Arithmetic operators
	//=============================================================================================
	// +
	template<typename VectorA, typename VectorB,
			 typename light::enable_if<is_vector<VectorA>::value, int>::type = 0>
	typename result_type<VectorA, VectorB>::type operator + (VectorA const &a, VectorB const &b)
	{
		assert(RED_DIMENSION(a) == RED_DIMENSION(b));

		typedef typename result_type<VectorA, VectorB>::type res_type;
		res_type result;
		result.resize(RED_DIMENSION(a));

		size_t dim = RED_DIMENSION(a);
		for(size_t i = 0; i < dim; i++)
			result[i] = a[i] + b[i];

		return result;
	}

	// -
	template<typename VectorA, typename VectorB,
			 typename light::enable_if<is_vector<VectorA>::value, int>::type = 0>
	typename result_type<VectorA, VectorB>::type operator - (VectorA const &a, VectorB const &b)
	{
		assert(RED_DIMENSION(a) == RED_DIMENSION(b));

		typedef typename result_type<VectorA, VectorB>::type res_type;
		res_type result;
		resize(result, RED_DIMENSION(a));

		for(size_t i = 0; i < RED_DIMENSION(a); i++)
			result[i] = a[i] - b[i];

		return result;
	}

	// negate
	template<typename Vector,
			 typename light::enable_if<is_vector<Vector>::value, int>::type = 0>
	typename unary_result_type<Vector>::type operator - (Vector const &a)
	{
		typename unary_result_type<Vector>::type result;
		resize(result, RED_DIMENSION(a));

		for(size_t i = 0; i < RED_DIMENSION(a); i++)
			result[i] = -a[i];

		return result;
	}

	// vec * vec
	template<typename VectorA, typename VectorB,
			 typename light::enable_if<is_vector<VectorA>::value, int>::type = 0>
	typename result_type<VectorA, VectorB>::type operator * (VectorA const &a, VectorB const &b)
	{
		assert(RED_DIMENSION(a) == RED_DIMENSION(b));

		typedef typename result_type<VectorA, VectorB>::type res_type;
		res_type result;
		resize(result, RED_DIMENSION(a));

		for(size_t i = 0; i < RED_DIMENSION(a); i++)
			result[i] = a[i] * b[i];

		return result;
	}

	// vec * scalar
	template<typename Vector,
			 typename light::enable_if<is_vector<Vector>::value, int>::type = 0>
	typename unary_result_type<Vector>::type operator * (Vector const &vec, typename Vector::real_type scalar)
	{
		typename unary_result_type<Vector>::type result;
		resize(result, RED_DIMENSION(vec));

		for(size_t i = 0; i < RED_DIMENSION(vec); i++)
			result[i] = vec[i] * scalar;

		return result;
	}

	// scalar * vec
	template<typename Vector,
			 typename light::enable_if<is_vector<Vector>::value, int>::type = 0>
	typename unary_result_type<Vector>::type operator * (typename Vector::real_type scalar, Vector const &vec)
	{
		typename unary_result_type<Vector>::type result;
		resize(result, RED_DIMENSION(vec));

		for(size_t i = 0; i < RED_DIMENSION(vec); i++)
			result[i] = vec[i] * scalar;

		return result;
	}

	// vec / vec
	template<typename VectorA, typename VectorB,
			 typename light::enable_if<is_vector<VectorA>::value, int>::type = 0>
	typename result_type<VectorA, VectorB>::type operator / (VectorA const &a, VectorB const &b)
	{
		assert(RED_DIMENSION(a) == RED_DIMENSION(b));

		typedef typename result_type<VectorA, VectorB>::type res_type;
		res_type result;
		resize(result, RED_DIMENSION(a));

		for(size_t i = 0; i < RED_DIMENSION(a); i++)
			result[i] = a[i] / b[i];

		return result;
	}

	// vec / scalar
	template<typename Vector,
			 typename light::enable_if<is_vector<Vector>::value, int>::type = 0>
	typename unary_result_type<Vector>::type operator / (Vector const &vec, typename Vector::real_type scalar)
	{
		typename unary_result_type<Vector>::type result;
		resize(result, RED_DIMENSION(vec));

		for(size_t i = 0; i < RED_DIMENSION(vec); i++)
			result[i] = vec[i] / scalar;

		return result;
	}

	// scalar * vec
	template<typename Vector,
			 typename light::enable_if<is_vector<Vector>::value, int>::type = 0>
	typename unary_result_type<Vector>::type operator / (typename Vector::real_type scalar, Vector const &vec)
	{
		typename unary_result_type<Vector>::type result;
		resize(result, RED_DIMENSION(vec));

		for(size_t i = 0; i < RED_DIMENSION(vec); i++)
			result[i] = vec[i] / scalar;

		return result;
	}


	//=============================================================================================
	// Geometric operations
	//=============================================================================================
	namespace internal
	{
		template<typename VectorA, typename VectorB>
		struct have_same_real_type
		{
			static bool const value = light::is_same<typename VectorA::real_type,
			                                         typename VectorB::real_type>::value;
		};
	}

	// Dot product
	template<typename VectorA, typename VectorB,
			 typename light::enable_if<internal::have_same_real_type<VectorA, VectorB>::value, int>::type = 0>
	typename VectorA::real_type dot(VectorA const &a, VectorB const &b)
	{
		assert(RED_DIMENSION(a) == RED_DIMENSION(b));

		typedef typename VectorA::real_type real_type;
		real_type result{0};
		for(size_t i = 0; i < RED_DIMENSION(a); i++)
			result += a[i] * b[i];

		return result;
	}

	// Squared length
	template<typename Vector,
			 typename light::enable_if<is_vector<Vector>::value, int>::type = 0>
	typename Vector::real_type length_sq(Vector const &vec)
	{
		return dot(vec, vec);
	}

	// Length
	template<typename Vector,
			 typename light::enable_if<is_vector<Vector>::value, int>::type = 0>
	typename Vector::real_type length(Vector const &vec)
	{
		return sqrt(dot(vec, vec));
	}

	// Cross product
	template<typename Real>
	static_vector<Real, 3> cross(static_vector<Real, 3> const &a, static_vector<Real, 3> const &b)
	{
		static_vector<Real, 3> result;

		result[0] = a[1] * b[2] - a[2] * b[1];
		result[1] = a[2] * b[0] - a[0] * b[2];
		result[2] = a[0] * b[1] - a[1] * b[0];

		return result;
	}

} // namespace: red
